32-бит­ная эпо­ха ухо­дит в прош­лое, сда­ваясь под натис­ком новых идей и плат­форм. Оба флаг­мана рын­ка (Intel и AMD) пред­ста­вили 64-бит­ные архи­тек­туры, откры­вающие дверь в мир боль­ших ско­рос­тей и про­изво­дитель­ных ЦП. Это нас­тоящий про­рыв — новые регис­тры, новые режимы работы… поп­робу­ем с ними разоб­рать­ся? Мы рас­смот­рим архи­тек­туру AMD64 (она же x86-64) и покажем, как с ней бороть­ся.

64-бит­ный лей­бл — зву­чит воз­бужда­юще, но в прак­тичес­ком пла­не это все­го лишь хит­рый мар­кетин­говый трюк, скры­вающий не толь­ко дос­тоинс­тва, но и недос­татки. Нам дарова­ны 64-бит­ные опе­ран­ды и 64-бит­ная адре­сация. Казалось бы, лиш­ние раз­ряды кар­ман не тянут и если не при­годят­ся, то по край­ней мере не помеша­ют. Так ведь нет! С рос­том раз­ряднос­ти уве­личи­вает­ся и дли­на машин­ных команд, а зна­чит, вре­мя их заг­рузки/декоди­рова­ния и раз­меры прог­раммы, поэто­му для дос­тижения не худ­шей про­изво­дитель­нос­ти 64-бит­ный про­цес­сор дол­жен иметь более быс­трую память и более емкий кеш. Это раз.

64-бит­ные целочис­ленные опе­ран­ды ста­новят­ся юза­бель­ны толь­ко при обра­бот­ке чисел поряд­ка 2^33 + (8 589 934 592) и выше. Там, где 32-бит­ному про­цес­сору тре­бует­ся нес­коль­ко так­тов, 64-бит­ный справ­ляет­ся за один. Но где ты видел такие чис­ла в домаш­них и офис­ных при­ложе­ниях? Не зря же инже­неры из Intel пош­ли на сок­ращение раз­ряднос­ти АЛУ (ариф­метико‑логичес­кого устрой­ства), ширина которо­го в Pentium 4 сос­тавля­ет все­го 16 бит, про­тив 32 бит в Pentium III. Это не зна­чит, что Pentium 4 не может обра­баты­вать 32-раз­рядные чис­ла. Может. Толь­ко он тра­тит на них боль­ше вре­мени, чем Pentium III. Но, пос­коль­ку про­цент под­линно 32-раз­рядных чисел (то есть таких, что исполь­зуют свы­ше 16 бит) в домаш­них при­ложе­ниях отно­ситель­но невысок, про­изво­дитель­ность пада­ет нез­начитель­но. Зато ядро содер­жит мень­ше тран­зисто­ров, выделя­ет мень­ше теп­ла и луч­ше работа­ет на повышен­ной так­товой час­тоте — в целом эффект положи­тель­ный.

64-бит­ная раз­рядность… Помилуй! Адре­совать 18 446 744 073 709 551 616 байт памяти не нуж­но даже Microsoft’у со все­ми его гра­фичес­кими заворо­тами! Из 4 Гбайт адресно­го прос­транс­тва Windows Processional и Windows Server толь­ко 2 Гбайт выделя­ют при­ложе­ниям.

3 Гбайт выделя­ет лишь Windows Advanced Server, и не потому, что боль­ше выделить невоз­можно! x86-про­цес­соры с лег­костью адре­суют вплоть до 16 Гбайт (по 4 Гбайт на код, дан­ные, стек и кучу), опять‑таки обхо­дясь минималь­ной перес­трой­кой опе­раци­онной сис­темы! Почему же до сих пор это не было сде­лано? Почему мы сидим на жал­ких 4 Гбайт, из которых реаль­но дос­тупны толь­ко два?! Да потому, что боль­ше никому не нуж­но! Сис­тему, адре­сующую 16 Гбайт, прос­то так не про­дашь, кого эти гигабай­ты инте­ресу­ют? Вот 64 бита — сов­сем дру­гое дело! Это осве­жает! Вот все вок­руг них и тан­цуют.

Срав­нивать 32- и 64-бит­ные про­цес­соры бес­смыс­ленно. Если 64-бит­ный про­цес­сор на домаш­нем при­ложе­нии ока­зыва­ется быс­трее, то отнюдь не за счет сво­ей 64-бит­ности, а бла­года­ря совер­шенно незави­симым от нее конс­трук­тивным ухищ­рени­ям, на которых инже­неры едва не разор­вали себе зад­ницы!

Впро­чем, не будем о грус­тном. 64 бита все рав­но вой­дут в нашу жизнь. Для некото­рых задач они очень даже ничего. Вот, нап­ример, крип­тогра­фия. 64 бита — это же 8 байт! 8-сим­воль­ные пароли мож­но пол­ностью умес­тить в один регистр, не обра­щаясь к памяти, что дает неверо­ятный резуль­тат! Ско­рость перебо­ра уве­личи­вает­ся чуть ли не на порядок! Ну так чего же мы ждем? Впе­ред! На штурм 64-бит­ных вер­шин!

AMD Athlon 64 во всей своей красе
AMD Athlon 64 во всей сво­ей кра­се
 

Что нам понадобится?

Нам пот­ребу­ется 64-раз­рядная опе­раци­онная сис­тема. Дотянуть­ся до 64-бит­ных регис­тров и про­чих вкус­ностей x86-64-архи­тек­туры мож­но толь­ко из спе­циаль­ного 64-раз­рядно­го режима (long mode). Ни под реаль­ным, ни под 32-раз­рядным защищен­ным x86-режимом они не дос­тупы. И хотя мы покажем, как перевес­ти про­цес­сор из реаль­ного в 64-раз­рядный режим, соз­дание пол­нофун­кци­ональ­ной опе­раци­онной сис­темы не вхо­дит в наши пла­ны, а без нее никуда!

Те­перь перей­дем к под­готов­ке инс­тру­мен­тария. Как минимум нам понадо­бит­ся ассем­блер и отладчик. Мы будем исполь­зовать FASM. Он бес­пла­тен, работа­ет под Linux, Windows и MS-DOS, под­держи­вает x86-64 и обла­дает удоб­ным син­такси­сом.

Прак­тичес­ки во все x86-64-пор­ты Linux вхо­дит GNU Debugger, которо­го для наших задач впол­не дос­таточ­но. Обла­дате­ли Windows могут вос­поль­зовать­ся Microsoft Debugger.

 

Обзор x86-64

За под­робным опи­сани­ем x86-64-архи­тек­туры луч­ше все­го обра­тить­ся к фир­менной докумен­тации AMD64 Technology — AMD64 Architecture Programmer’s Manual Volume 1:Application Programming. Мы же огра­ничим­ся толь­ко бег­лым обзо­ром основных новов­ведений.

На­конец‑то AMD сжа­лилась над нами и подари­ла прог­раммис­там то, чего все так дол­го жда­ли. К семи регис­трам обще­го наз­начения (вось­ми — с уче­том ESP) добави­лось еще восемь, в резуль­тате чего их общее количес­тво дос­тигло 15 (16) штук.

Ста­рые регис­тры, рас­ширен­ные до 64 бит, получи­ли име­на RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP, RIP и RFLAGS. Новые регис­тры оста­лись безымян­ными и прос­то про­нуме­рова­ны от R8 до R15. Для обра­щения к млад­шим 8, 16 и 32 битам новых регис­тров мож­но исполь­зовать суф­фиксы b, w и d. Нап­ример, R9 — это 64-раз­рядный регистр, R9b — его млад­ший байт (по ана­логии с AL), а R9w — млад­шее сло­во (то же самое, что AX в EAX). Пря­мых нас­ледни­ков AH, к сожале­нию, не наб­люда­ется, и для манипу­ляции со сред­ней частью регис­тров при­ходит­ся извра­щать­ся со сдви­гами и матема­тичес­кими опе­раци­ями.

Регистры, доступные в режиме x86-64
Ре­гис­тры, дос­тупные в режиме x86-64

Ре­гистр, ука­затель команд RIP, теперь адре­сует­ся точ­но так же, как и все осталь­ные регис­тры обще­го наз­начения. Прог­раммис­ты, зас­тавшие живую PDP-11 (или ее оте­чес­твен­ный клон — «Элек­тро­нику БК», или УКНЦ), толь­ко през­ритель­но хмык­нут: наконец‑то до раз­работ­чиков ста­ли доходить оче­вид­ные исти­ны, которые на всех нор­маль­ных плат­формах были реали­зова­ны еще неиз­вес­тно ког­да.

Возь­мем прос­тей­ший при­мер: заг­рузим в регистр AL опкод сле­дующей машин­ной коман­ды. На x86 при­ходит­ся пос­тупать так.

Заг­рузка опко­да сле­дующей машин­ной коман­ды в клас­сичес­ком x86
call $ + 5 ; Запихнуть в стек адрес следующей команды и передать на нее управление
pop ebx ; Вытолкнуть из стека адрес возврата
add ebx, 6 ; Скорректировать адрес на размер команд pop/add/mov
mov al, [ebx] ; Теперь AL содержит опкод команды NOP
NOP ; Команда, чей опкод мы хотим загрузить в AL

Это же умом поехать мож­но, пока все это напишешь! И еще здесь очень лег­ко оши­бить­ся в раз­мере команд — при­ходит­ся вычис­лять его вруч­ную либо заг­ромож­дать лис­тинг никому не нуж­ными мет­ками. К тому же неиз­бежно зат­рагива­ется стек, что в ряде слу­чаев нежела­тель­но или недопус­тимо (осо­бен­но в защит­ных механиз­мах, наш­пигован­ных анти­отла­доч­ными при­ема­ми).

А теперь перепи­шем тот же самый при­мер на x86-64.

Заг­рузка опко­да сле­дующей машин­ной коман­ды на x86-64
mov al,[rip] ; Загружаем опкод следующей машинной команды
NOP ; Команда, чей опкод мы хотим загрузить в AL

Кра­сота! Толь­ко сле­дует пом­нить, что RIP всег­да ука­зыва­ет на сле­дующую, а отнюдь не текущую инс­трук­цию! К сожале­нию, ни Jx RIP, ни CALL RIP не работа­ют. Таких команд в лек­сиконе x86-64 прос­то нет.

Но это еще что! Исчезла абсо­лют­ная адре­сация! Если нам надо изме­нить содер­жимое ячей­ки памяти по кон­крет­ному адре­су, на x86 мы пос­тупа­ем приб­лизитель­но так:

dec byte ptr [666h] ; Уменьшить содержимое байта по адресу 666h на единицу

Под x86-64 тран­сля­тор выда­ет ошиб­ку ассем­бли­рова­ния, вынуж­дая нас при­бегать к фик­тивно­му базиро­ванию:

xor r9, r9 ; Обнулить регистр r9
dec byte ptr [r9+666h] ; Уменьшить содержимое байта по адресу 0+666h на единицу

Есть и дру­гие отли­чия от x86, но они не столь прин­ципи­аль­ны. Важ­но то, что в режиме сов­мести­мос­ти с x86 (Legacy Mode) ни 64-бит­ные регис­тры, ни новые методы адре­сации не дос­тупны! Никаки­ми средс­тва­ми (вклю­чая чер­ную и белую магию) дотянуть­ся до них нель­зя, и, преж­де чем что‑то сде­лать, необ­ходимо перевес­ти про­цес­сор в длин­ный режим (long mode), который делит­ся на два под­режима: режим сов­мести­мос­ти с x86 (compatibility mode) и 64-бит­ный режим (64-bit mode). Режим сов­мести­мос­ти пре­дус­мотрен толь­ко для того, что­бы 64-раз­рядная опе­раци­онная сис­тема мог­ла выпол­нять ста­рые 32-бит­ные при­ложе­ния. Никакие 64-бит­ные регис­тры здесь и не ночева­ли!

Ре­аль­ная 64-бит­ность оби­тает толь­ко в 64-bit long mode, о котором мы и будем говорить.

 

Hello, world на x86-64

Прог­рамми­рова­ние под 64-бит­ную вер­сию Windows мало чем отли­чает­ся от тра­дици­онно­го, толь­ко все опе­ран­ды и адре­са по умол­чанию 64-раз­рядные, а парамет­ры API-фун­кций переда­ются боль­шей частью через регис­тры, а не через стек. Пер­вые четыре аргу­мен­та всех API-фун­кций переда­ются в регис­трах RCX, RDX, R8 и R9 (регис­тры перечис­лены в поряд­ке сле­дова­ния аргу­мен­тов, край­ний левый аргу­мент помеща­ется в RCX). А уж осталь­ные парамет­ры кла­дут­ся в стек. Все это называ­ется x86-64 fast calling conversion (сог­лашение о быс­трой переда­че парамет­ров для x86-64), под­робное опи­сание мож­но най­ти в статье The history of calling conventions, part 5 amd64. Так­же советую заг­лянуть на стра­нич­ку бес­плат­ного ком­пилято­ра Free PASCAL и под­нять докумен­тацию по спо­собам вызова API.

В час­тнос­ти, вызов фун­кции с пятью аргу­мен­тами API_func(1,2,3,4,5) выг­лядит так:

mov dword ptr [rsp+20h], 5 ; Кладем на стек пятый слева аргумент
mov r9d, 4 ; Передаем четвертый слева аргумент
mov r8d, 3 ; Передаем третий слева аргумент
mov edx, 2 ; Передаем второй слева аргумент
mov ecx, 1 ; Передаем первый слева аргумент
call API_func

Сме­щение пятого аргу­мен­та отно­ситель­но вер­хушки сте­ка тре­бует пояс­нений. Почему оно рав­но 20h? Ведь адрес воз­вра­та занима­ет толь­ко 8 байт, кто же съел все осталь­ные? Ока­зыва­ется, они резер­виру­ются для пер­вых четырех аргу­мен­тов, передан­ных через регис­тры. Зарезер­вирован­ные ячей­ки содер­жат неини­циали­зиро­ван­ный мусор и по‑бур­жуй­ски называ­ются spill, что перево­дит­ся как затыч­ка или потеря.

Вот минимум зна­ний, необ­ходимых для выжива­ния в мире 64-бит­ной Windows при прог­рамми­рова­нии на ассем­бле­ре. Оста­ется разоб­рать самую малость: как эти самые 64-бита заполу­чить? Для перево­да FASM’а в режим x86-64 дос­таточ­но ука­зать дирек­тиву use64 и даль­ше кодить как обыч­но.

Ни­же идет при­мер прос­тей­шей x86-64-прог­раммы, которая не дела­ет ничего, толь­ко воз­вра­щает в регис­тре RAX зна­чение 0.

; Сообщаем FASM’у, что мы хотим программировать на x86-64
use64
xor r9,r9 ; Обнуляем регистр r9
mov rax,r9 ; Пересылаем в rax,r9 (можно сразу mov rax,0, но неинтересно)
ret ; Выходим туда, откуда пришли

Ни­каких допол­нитель­ных аргу­мен­тов коман­дной стро­ки ука­зывать не надо, прос­то ска­зать fasm file-name.asm, и все! Через мгно­вение обра­зует­ся файл file-name.bin, который в hex-пред­став­лении выг­лядит так:

Ди­зас­сем­блер­ный лис­тинг прос­тей­шей 64-бит­ной прог­раммы
4D 31 C9 xor r9, r9
4C 89 C8 mov rax, r9
C3 retn

Фор­маль­но это типич­ный com-файл, вот толь­ко запус­тить его не удас­тся (во вся­ком слу­чае, ни одна популяр­ная ось его не съест), необ­ходимо замутить закон­ченный ELF или PE, в заголов­ке которо­го будет явно про­писа­на нуж­ная раз­рядность.

На­чиная с вер­сии 1.64 ассем­блер FASM под­держи­вает спе­циаль­ную дирек­тиву format PE64", авто­мати­чес­ки фор­миру­ющую 64-раз­рядный PE-файл (дирек­тиву use64 в этом слу­чае ука­зывать уже не нуж­но), а в катало­ге EXAMPLES мож­но най­ти готовый при­мер PE64DEMO, в котором показа­но, как ее исполь­зовать на прак­тике.

Ни­же при­веден при­мер x86-64-прог­раммы Hello, world с ком­мента­риями.

64-бит­ное при­ложе­ние Hello, world под Windows на FASM’е
; Пример 64-битного PE файла
; Для его выполнения необходимо иметь Windows XP 64-bit edition
; Указываем формат
format PE64 GUI
; Указываем точку входа
entry start
; Создать кодовую секцию с атрибутами на чтение и исполнение
section '.code' code readable executable
start:
mov r9d,0 ; uType == MB_OK (кнопка по умолчанию)
; Аргументы по соглашению x86-64
; передаются через регистры, не через стек!
; Префикс d задает регистр размером в слово,
; можно использовать и mov r9,0, но тогда
; машинный код будет на байт длиннее
lea r8,[_caption] ; lpCaption передаем смещение
; Команда lea занимает всего 7 байт,
; а mov reg, offset — целых 11, так что
; lea намного более предпочтительна
lea rdx,[_message] ; lpText передаем смещение выводимой строки
mov rcx,0 ; hWnd передам дескриптор окна владельца
; (можно также использовать xor rcx, rcx,
; что на три байта короче)
call [MessageBox] ; Вызываем функцию MessageBox
mov ecx,eax ; Заносим в ecx результат возврата
; (функция ExitProcess ожидает 32-битный параметр,
; можно использовать и mov rcx, rax, но это будет
; на байт длиннее)
call [ExitProcess] ; Вызываем функцию ExitProcess
; Создать секцию данных с атрибутами на чтение и запись
; (вообще-то в данном случае атрибут на запись необязателен,
; поскольку мы ничего не пишем, а только читаем)
section '.data' data readable writeable
_caption db 'PENUMBRA is awesome!',0 ; ASCIIZ-строка заголовка окна
_message db 'Hello World!',0 ; ASCIIZ-строка, выводимая на экран
; Создать секцию импорта с атрибутами на чтение и запись
; (здесь атрибут на запись обязателен, поскольку при загрузке PE-файла
; в секцию импорта будут записываться фактические адреса API-функций)
section '.idata' import data readable writeable
dd 0,0,0,RVA kernel_name,RVA kernel_table
dd 0,0,0,RVA user_name,RVA user_table
dd 0,0,0,0,0 ; Завершаем список двумя 64-разрядными нулями!
kernel_table:
ExitProcess dq RVA _ExitProcess
dq 0 ; Завершаем список 64-разрядным нулем!
user_table:
MessageBox dq RVA _MessageBoxA
dq 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
db 'ExitProcess',0
_MessageBoxA dw 0
db 'MessageBoxA',0
64-битный файл — первый полет
64-бит­ный файл — пер­вый полет

Ас­сем­бли­руем файл (fasm PE64DEMO.ASM) и запус­тим обра­зовав­ший­ся EXE на выпол­нение. Под 32-раз­рядной Windows он, естес­твен­но, не запус­тится.

Реакция 32-битной Windows на попытку запуска 64-битного PE-файла
Ре­акция 32-бит­ной Windows на попыт­ку запус­ка 64-бит­ного PE-фай­ла

Вдо­воль наиг­равшись нашим пер­вым x86-64 фай­лом, заг­рузим его в дизас­сем­блер (нап­ример, в IDA Pro 4.7. Она хоть и материт­ся, пред­лагая исполь­зовать спе­циаль­ную 64-бит­ную вер­сию, но при нажатии на Yes все кор­рек­тно дизас­сем­бли­рует. Во вся­ком слу­чае до тех пор, пока не стол­кнет­ся с под­линным 64-бит­ным адре­сом или опе­ран­дом, который будет обре­зан, в час­тнос­ти mov r9,1234567890h дизас­сем­бли­рует­ся как mov r9, 34567890h. Так что переход на 64-бит­ную вер­сию IDA все же очень желате­лен, тем более что начиная с IDA 4.9 она вхо­дит в базовую пос­тавку). Пос­мотрим, что у нашей прог­раммы внут­ри?

Ди­зас­сем­блер­ный лис­тинг 64-бит­ного при­ложе­ния Hello, world
.code:0000000000401000 41 B9 00 00 00 00 mov r9d, 0
.code:0000000000401006 4C 8D 05 F3 0F 00 00 lea r8, aPENUMBRA
.code:000000000040100D 48 8D 15 03 10 00 00 lea rdx, aHelloWorld ; "Hello World!"
.code:0000000000401014 48 C7 C1 00 00 00 00 mov rcx, 0
.code:000000000040101B FF 15 2B 20 00 00 call cs:MessageBoxA
.code:0000000000401021 89 C1 mov ecx, eax
.code:0000000000401023 FF 15 13 20 00 00 call cs:ExitProcess

Что ж, доволь­но гро­моз­дко, объ­емно и кон­цепту­аль­но. Для срав­нения дизас­сем­бли­рован­ный лис­тинг ана­логич­ного 32-раз­рядно­го фай­ла при­веден ниже. Ста­рый x86-код в 1,6 раза короче! А ведь это толь­ко демонс­тра­цион­ная прог­рамма из нес­коль­ких строк! На пол­новес­ных при­ложе­ниях раз­рыв будет толь­ко нарас­тать. Так что не сто­ит зло­упот­реблять 64-раз­рядным кодом без необ­ходимос­ти. Его сле­дует исполь­зовать толь­ко там, где 64-бит­ная ариф­метика и восемь допол­нитель­ных регис­тров дей­стви­тель­но дают ощу­тимый выиг­рыш. Нап­ример, в матема­тичес­ких задачах или прог­раммах для вскры­тия паролей.

Дизассемблирование 64-битного PE-файла 32-битной версией IDA Pro
Ди­зас­сем­бли­рова­ние 64-бит­ного PE-фай­ла 32-бит­ной вер­сией IDA Pro
Ди­зас­сем­блер­ный лис­тинг 32-бит­ного при­ложе­ния Hello, world
code:00401000 6A 00 push 0
code:00401002 68 00 20 40 00 push offset aPENUMBRA
code:00401007 68 17 20 40 00 push offset aHelloWorld
code:0040100C 6A 00 push 0
code:0040100E FF 15 44 30 40 00 call ds:MessageBoxA
code:00401014 6A 00 push 0
code:00401016 FF 15 3C 30 40 00 call ds:ExitProcess

В качес­тве зак­лючитель­ного упражне­ния перепи­шем наше при­ложе­ние в сти­ле MASM, пок­лонни­ков которо­го нуж­но не бить, а ува­жать. Никаких радикаль­ных отли­чий не наб­люда­ется:

64-бит­ное при­ложе­ние Hello, world под Windows на MASM’е
; Объявляем внешние API-функции, которые мы будем вызывать
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
; Секция данных с атрибутами по умолчанию (чтение и запись)
.data
mytit db 'PENUMBRA is awesome!', 0
mymsg db 'Hello World!', 0
; Секция кода с атрибутами по умолчанию (чтение и исполнение)
.code
Main:
mov r9d, 0 ; uType = MB_OK
lea r8, mytit ; LPCSTR lpCaption
lea rdx, mymsg ; LPCSTR lpText
mov rcx, 0 ; hWnd = HWND_DESKTOP
call MessageBoxA
mov ecx, eax ; uExitCode = MessageBox(...)
call ExitProcess
End Main

Ас­сем­бли­рова­ние и лин­ковка про­ходит так:

ml64 XXX.asm /link /subsystem:windows /defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main

В резуль­тате чего обра­зует­ся готовый к упот­ребле­нию EXE-файл с румяной под­жарен­ной короч­кой нашего ЦП (FASM ассем­бли­рует нам­ного быс­трее).

При­меры более слож­ных прог­рамм лег­ко най­ти в сети. Как показы­вает прак­тика, зап­росы типа x86-64 [AMD64] assembler example катас­тро­фичес­ки неэф­фектив­ны и гораз­до луч­ше исполь­зовать что‑нибудь вро­де mov rax.

 

Заключение

Вот мы и поз­накоми­лись с архи­тек­турой x86-64! Здесь дей­стви­тель­но есть где раз­вернуть­ся и чему поучить­ся! Нас­коль­ко эти зна­ния ока­жут­ся вос­тре­бован­ны на прак­тике — так сра­зу и не ска­жешь. У AMD есть хорошие шан­сы пошат­нуть рынок, но ведь и Intel не дрем­лет, активно прод­вигая собс­твен­ные 64-раз­рядные плат­формы, извес­тные под общим име­нем IA64, но о них как‑нибудь в дру­гой раз.

Переход в 64-разрдяный режим

В исходни­ках FreeBSD мож­но най­ти файл amd64_tramp.S, быс­тро и гряз­но перево­дящий про­цес­сор в 64-раз­рядный режим. Откомпи­лиро­вав, его мож­но записать в boot-сек­тор, заг­ружа­ющий нашу собс­твен­ную опе­раци­онную сис­тему (ты ведь пишешь ее, прав­да?) или слин­ковать com-файл, запус­каемый из реаль­ного x86-режима (для это­го пот­ребу­ется чис­тая MS-DOS безо вся­ких экстен­деров). В общем, вари­антов мно­го.

Пе­ревод про­цес­сора в 64-раз­рядный режим
//$FreeBSD: /repoman/r/ncvs/src/sys/boot/i386/libi386/amd64_tramp.S,v 1.4 2004/05/14
/*
* Quick and dirty trampoline to get into 64 bit (long) mode and running
* with paging enabled so that we enter the kernel at its linked address.
*/
#define MSR_EFER 0xc0000080
#define EFER_LME 0x00000100
#define CR4_PAE 0x00000020
#define CR4_PSE 0x00000010
#define CR0_PG 0x80000000
/* GRRR. Deal with BTX that links us for a non-zero location */
#define VPBASE 0xa000
#define VTOP(x) ((x) + VPBASE)
.data
.p2align 12,0x40
.globl PT4
PT4:
.space 0x1000
.globl PT3
PT3:
.space 0x1000
.globl PT2
PT2:
.space 0x1000
gdtdesc:
.word gdtend - gdt
.long VTOP(gdt) # low
.long 0 # high
gdt:
.long 0 # null descriptor
.long 0
.long 0x00000000 # %cs
.long 0x00209800
.long 0x00000000 # %ds
.long 0x00008000
gdtend:
.text
.code32
.globl amd64_tramp
amd64_tramp:
/* Be sure that interrupts are disabled */
cli
/* Turn on EFER.LME */
movl $MSR_EFER, %ecx
rdmsr
orl $EFER_LME, %eax
wrmsr
/* Turn on PAE */
movl %cr4, %eax
orl $(CR4_PAE | CR4_PSE), %eax
movl %eax, %cr4
/* Set %cr3 for PT4 */
movl $VTOP(PT4), %eax
movl %eax, %cr3
/* Turn on paging (implicitly sets EFER.LMA) */
movl %cr0, %eax
orl $CR0_PG, %eax
movl %eax, %cr0
/* Now we’re in compatibility mode. set %cs for long mode */
movl $VTOP(gdtdesc), %eax
movl VTOP(entry_hi), %esi
movl VTOP(entry_lo), %edi
lgdt (%eax)
ljmp $0x8, $VTOP(longmode)
.code64
longmode:
/* We're still running V=P, jump to entry point */
movl %esi, %eax
salq $32, %rax
orq %rdi, %rax
pushq %rax
ret

Оцени статью:

Что тебе понравилось больше всего?
Что тебе не понравилось больше всего?